import { isMap } from '../utils/type'

export const ITERATE_KEY = Symbol('ITERATE_KEY') // for...in...遍历key的时候的，需要track(target, 一个独有的key)
export const MAP_KEY_ITERATE_KEY = Symbol('MAP_KEY_ITERATE_KEY') //  map在set已有key的时候，keys()的effectFn其实是不需要执行的， 单独处理这一种情况
export const TriggerType = {
  SET: 'SET',
  ADD: 'ADD',
  DELETE: 'DELETE',
}

const bucket = new WeakMap()

let activeEffect
const effectStack = []
/**
 *
 * @param {Function} fn
 * @param {{lazy: boolean, scheduler: Function}} options
 */
export function effect(fn, options = {}) {
  const effectFn = () => {
    cleanup(effectFn)
    activeEffect = effectFn
    effectStack.push(effectFn)
    const res = fn()
    effectStack.pop()
    activeEffect = effectStack[effectStack.length - 1]
    return res
  }
  effectFn.options = options
  effectFn.deps = []
  if (options.lazy) {
    return effectFn
  } else {
    effectFn()
  }
}

function cleanup(effectFn) {
  for (let i = 0; i < effectFn.deps.length; i++) {
    const deps = effectFn.deps[i]
    deps.delete(effectFn)
  }
  effectFn.deps.length = 0
}

export function track(target, key, shouldTrack = true) {
  if (!activeEffect || !shouldTrack) return
  let depsMap = bucket.get(target)
  if (!depsMap) bucket.set(target, (depsMap = new Map()))
  let deps = depsMap.get(key)
  if (!deps) depsMap.set(key, (deps = new Set()))
  deps.add(activeEffect)
  activeEffect.deps.push(deps)
}

/**
 *
 * @param {Object} target
 * @param {string | Symbol} key
 * @param {'SET' | 'ADD'} type 区分是新增属性还是修改属性
 */
export function trigger(target, key, type, val) {
  const depsMap = bucket.get(target)
  if (!depsMap) return
  const deps = depsMap.get(key)
  const effectsToRun = new Set()
  deps &&
    deps.forEach((effectFn) => {
      if (effectFn !== activeEffect) {
        effectsToRun.add(effectFn)
      }
    })
  // for...in...关联的ITERATE_KEY的effectFn也要执行, 在新增或删除属性的情况下执行effectFn
  // Map类型， set时，也需要执行ITERATE_KEY的effectFn
  if (
    type === TriggerType.ADD ||
    type === TriggerType.DELETE ||
    (isMap(target) && type === TriggerType.SET)
  ) {
    const iterateEffects = depsMap.get(ITERATE_KEY)
    iterateEffects &&
      iterateEffects.forEach((effectFn) => {
        if (effectFn !== activeEffect) {
          effectsToRun.add(effectFn)
        }
      })
  }
  // Map类型， set以后key时，不需要触发keys()的effectFn, 只有引发map的size变化时，需要执行keys()的effectFn
  if (
    (type === TriggerType.ADD || type === TriggerType.DELETE) &&
    isMap(target)
  ) {
    const iterateEffects = depsMap.get(MAP_KEY_ITERATE_KEY)
    iterateEffects &&
      iterateEffects.forEach((effectFn) => {
        if (effectFn !== activeEffect) {
          effectsToRun.add(effectFn)
        }
      })
  }
  // 数组对象的添加操作拦截
  if (Array.isArray(target) && type === TriggerType.ADD) {
    const lengthEffects = depsMap.get('length')
    lengthEffects &&
      lengthEffects.forEach((effectFn) => {
        if (effectFn !== activeEffect) {
          effectsToRun.add(effectFn)
        }
      })
  }
  // 数组修改length的操作
  if (Array.isArray(target) && key === 'length') {
    depsMap.forEach((effectFnDeps, effectKey) => {
      // effectKey就是数组的index， 相当于给数组的特定项注册了effect， length修改时，要把length的val以外的effect都执行掉(执行后会cleanup)
      if (effectKey >= val) {
        effectFnDeps.forEach((effectFn) => {
          if (effectFn !== activeEffect) {
            effectsToRun.add(effectFn)
          }
        })
      }
    })
  }
  effectsToRun.forEach((effectFn) => {
    if (effectFn.options.scheduler) {
      effectFn.options.scheduler(effectFn)
    } else {
      effectFn()
    }
  })
}

/**
 * 计算属性
 * @param {Function} getter
 * @returns {val:any}
 */
export function computed(getter) {
  let val
  let dirty = true
  const effectFn = effect(getter, {
    lazy: true,
    scheduler() {
      dirty = true
      trigger(obj, 'value')
    },
  })
  const obj = {
    get value() {
      if (dirty) {
        val = effectFn()
        dirty = false
      }
      track(obj, 'value')
      return val
    },
  }
  return obj
}

export function watch(source, cb, options) {
  let getter
  let oldVal, newVal, cleanup
  if (typeof source === 'function') {
    getter = source
  } else {
    getter = () => traverse(source)
  }
  function onInvalidate(fn) {
    cleanup = fn
  }
  const effectFn = effect(getter, {
    lazy: true,
    scheduler() {
      if (options.flush === 'post') {
        const p = Promise.resolve()
        p.then(job)
      } else {
        job()
      }
    },
  })
  const job = () => {
    newVal = effectFn()
    if (cleanup) cleanup()
    cb(newVal, oldVal, onInvalidate)
    oldVal = newVal
  }
  if (options.immediate) {
    job()
  } else {
    oldVal = effectFn()
  }
}

function traverse(val, seen = new Set()) {
  if (typeof val !== 'object' || val == null || seen.has(val)) return
  seen.add(val)
  for (const key of Object.keys(val)) {
    traverse(val[key], seen)
  }
  return val
}
